// 需要向输出html文件中添加CDN脚本引用
const { ExternalModule } = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 在打包生产模块的时候，截断正常打包逻辑，变成外部以来模块
class AutoExternalPlugin {
  constructor(options) {
    this.options = options;
    //可以进行外部依赖的模块数组
    this.externalModules = Object.keys(this.options); //['lodash']
    //存放着所有的实际用到的外部依赖
    this.importedModules = new Set();
  }
  //1.收集依赖，我们需要知道这个项目一共到底用了哪些外部依赖模块，放到importedModules里面
  //2.拦截生成模块的流程，如果它是一个外部模块，就不要走远时的打包流程了，而用一个外部模块进行替代
  //3.把使用到的依赖模块对应的CDN脚本插入到输出的index.html里面去
  apply(compiler) {
    //获取普通模块的工厂
    compiler.hooks.normalModuleFactory.tap(
      "autoExternalPlugin",
      (normalModuleFactory) => {
        normalModuleFactory.hooks.parser
          .for("javascript/auto")
          .tap("AutoExternalPlugin", (parser) => {
            //parser ast语法解析器，会负责把源代码转成AST语法树，并且进行遍历，如果发现了import语法，就会触发回调
            parser.hooks.import.tap(
              "AutpExternalPlugin",
              (statement, source) => {
                if (this.externalModules.includes(source)) {
                  this.importedModules.add(source); //如果走到了这里，就表示代码中实际用到了lodash模块
                }
              }
            );
            //监听CallExpression语法树节点，如果方法名是require的话
            parser.hooks.call
              .for("require")
              .tap("AutpExternalPlugin", (callExpression) => {
                let source = callExpression.arguments[0].value;
                if (this.externalModules.includes(source)) {
                  this.importedModules.add(source); //如果走到了这里，就表示代码中实际用到了lodash模块
                }
              });
          });
        normalModuleFactory.hooks.factorize.tapAsync(
          "AutoExternalPlugin",
          (resolveData, callback) => {
            let { request } = resolveData;
            if (this.importedModules.has(request)) {
              let { varName } = this.options[request];
              //如果返回的是一个外部模块，就不需要按中场模块生产流程执行
              callback(null, new ExternalModule(varName));
            } else {
              //读取模块源代码，传递loader再返回JS模块，在解析依赖，再返回此模块
              callback(null); //normalModule普通模块
            }
          }
        );
      }
    );
    compiler.hooks.compilation.tap("AutoRxternalPlugin", (compilation) => {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(
        "AutoExternalPlugin",
        (htmlData, callback) => {
          //1.HtmlWebpackPlugin内部会香complication对象上添加额外的钩子
          //2.可以通过HtmlWebpackPlugin.getHooks取出这些钩子
          //3.改变标签
          console.log(htmlData.assetTags.scripts[0]);
          Reflect.ownKeys(this.options)
            .filter((key) => this.importedModules.has(key))
            .forEach((key) => {
              htmlData.assetTags.scripts.unshift({
                tagName: "script",
                voidTag: false,
                meta: { plugin: "html-webpack-plugin" },
                attributes: { src: this.options[key].url },
              });
            });
          callback(null, htmlData);
        }
      );
    });
  }
}

module.exports = AutoExternalPlugin;
